import { Inject, Injectable } from '@nestjs/common';
import {
  FindManyOptions,
  FindOneOptions,
  FindOptionsWhere,
  Repository,
} from 'typeorm';
import { User } from './entities/user.entity';
import {
  LoginDto,
  RedactDto,
  RegisterDto,
  SendVerifyCodeDto,
} from './dto/user.dto';
import { HashUtil } from '../../common/utils/hash.util';
import { InjectRepository } from '@nestjs/typeorm';
import { AuthService } from '../sys/auth/auth.service';
import {
  generateVerfyCodeKey,
  RedisService,
} from '../shared/redis/redis.service';
import { AccountErrorKey } from '../../common/constants/user.constant';
import { Request } from 'express';
import { AccountType } from '../sys/auth/entities/login-log.entity';
import { EmailService } from '../shared/email/email.service';
import { generateFileName, MinioService } from '../shared/minio/minio.service';
import { extname, join } from 'path';
import {
  FileDirectory,
  PUBLIC_FILE_DIRECTORY,
} from '../../common/constants/minio.constant';
import { DictService } from '../sys/dict/dict.service';
import { IpUtil } from '../../common/utils/ip.util';
import { I18nContext, I18nService } from 'nestjs-i18n';
import { ResponseUtil } from '../../common/utils/response.util';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
    private readonly authService: AuthService,
    private readonly redisService: RedisService,
    private readonly emailService: EmailService,
    private readonly minioService: MinioService,
    private readonly dictService: DictService,
    @Inject(I18nService)
    private readonly i18n: I18nService,
  ) {}

  async exist(where: FindOptionsWhere<User>) {
    return await this.userRepository.existsBy(where);
  }

  async findOne(options: FindOneOptions<User>) {
    return await this.userRepository.findOne(options);
  }

  async findAndCount(opitons: FindManyOptions<User>) {
    const [rows, total] = await this.userRepository.findAndCount(opitons);
    return {
      rows,
      total,
    };
  }

  async sendVerifyCode(sendDto: SendVerifyCodeDto) {
    const redisKey = generateVerfyCodeKey(
      sendDto.method,
      sendDto.type,
      sendDto[sendDto.method],
    );
    const code = Math.random().toString().slice(-6);
    await this.redisService.set(redisKey, code, 'EX', 60 * 5);
    const content = `<p>您的验证码是：${code}, 有效期：${60 * 5} s</p>`;
    await this.emailService.send(sendDto.email, content, sendDto.type);
  }

  async register(registerDto: RegisterDto) {
    const { email, password } = registerDto;
    const isExist = await this.exist({ email });
    if (isExist) {
      throw new Error(AccountErrorKey.EXIST);
    }
    registerDto.password = HashUtil.encryption(password);
    await this.userRepository.save(this.userRepository.create(registerDto));
  }

  async login(loginDto: LoginDto, request: Request) {
    await this.authService.checkLoginIpBlackList(IpUtil.getRealIP(request));
    const { email, password } = loginDto;
    try {
      const user = await this.findOne({
        where: { email },
      });
      if (!user) throw new Error(AccountErrorKey.ACCOUNT_OR_PASSWORD_ERROR);
      HashUtil.checkPassword(password, user.password);
      return {
        user,
        token: await this.authService.getTokenAndCreateLoginLog(
          user.id,
          [],
          request,
          AccountType.USER,
        ),
      };
    } catch (error) {
      const count = await this.authService.setLoginIpBlackList(
        IpUtil.getRealIP(request),
      );
      const message = this.i18n.translate(
        `${ResponseUtil.index}.${error?.message || ''}`,
        {
          lang: I18nContext.current().lang,
          args: {
            ['count']: count,
          },
        },
      );
      throw new Error(message);
    }
  }

  async redactUserInfo(userId: string, redactDto: RedactDto) {
    return await this.userRepository.update({ id: userId }, redactDto);
  }

  async removeByPassword(userId: string, password: string) {
    const user = await this.findOne({ where: { id: userId } });
    if (!user) throw new Error(AccountErrorKey.NOT_EXIST);
    if (!HashUtil.compare(password, user.password)) {
      throw new Error(AccountErrorKey.PASSWORD_ERROR);
    }
    return await this.userRepository.softDelete(userId);
  }

  async uploadAndUpdateAvatar(
    originName: string,
    buffer: Buffer,
    userId: string,
  ) {
    const fileExtension = extname(originName);
    const fileName = join(
      `${PUBLIC_FILE_DIRECTORY}/${FileDirectory.USER_AVATAR}/${userId}`,
      generateFileName(buffer, fileExtension),
    ).replace(/\\/g, '/');
    await this.minioService.uploadFile(fileName, buffer);
    await this.userRepository.update({ id: userId }, { avatar: fileName });
    return await this.findOne({ where: { id: userId } });
  }

  async findUserGenderOptions() {
    return await this.dictService.findItemsByTypeIdOrCode('user_gender');
  }
}
